import preset from "./_presets.mjs";
import { cleanRules } from "./actions.mts";
import { alertLog, debug, features, speedOnly } from "./buildConfig.mts";
import { data } from "./globalData.mts";
import {
  defaultValues,
  getSavedHosts,
  gmMenu,
  promiseXhr,
  values,
} from "./gmBridge.mts";
import { logger, speed, countLength } from "./logger.mts";
import { initRules } from "./parse.mts";
import { CRRE, addTimeParameter, getEtag, isBasicRule } from "./util.mts";
import type { Types } from "tm";
import type {
  ListObject,
  XhrError,
  HikerResponse,
  AbpObject,
} from "./types.d.mts";

/**
 * 注册/刷新 更新脚本菜单
 * @async
 */
export async function makeInitMenu() {
  if (features.fromValues) await gmMenu("count", cleanRules);
  if (features.update)
    await gmMenu("update", () => {
      performUpdate(true)
        .then(() => {
          if (!debug) location.reload();
        })
        .catch(() => {
          if (debug) logger("error", "手动更新失败");
        });
    });
  return;
}

/**
 * 从 Response 的请求头部提取 ETag
 * @param resp Response
 * @returns {?string} ETag，未找到返回 null
 */
function extrEtag(
  resp: Types.Xhr.Response | HikerResponse | XMLHttpRequest,
): string | null {
  const etag = getEtag(
    typeof (resp as HikerResponse).headers == "object"
      ? // 海阔世界
        (resp as HikerResponse).headers.etag[0]
      : typeof (resp as Types.Xhr.Response).responseHeaders == "string"
        ? // Tampermonkey
          (resp as Types.Xhr.Response).responseHeaders
        : // Appara
          (resp as XMLHttpRequest).getAllResponseHeaders(),
  );
  debug &&
    !speedOnly &&
    logger("data", {
      monkey:
        typeof (resp as Types.Xhr.Response).responseHeaders == "string"
          ? (resp as Types.Xhr.Response).responseHeaders
          : null,
      hiker: (resp as HikerResponse).headers.etag[0],
      standard: (resp as XMLHttpRequest).getAllResponseHeaders(),
      parsed: etag,
    });
  return etag;
}

/**
 * 获取订阅规则：存储规则
 * @async
 * @param rule 订阅规则对象
 * @param resp Response
 * @returns {Promise.<void>}
 */
async function storeRule(
  rule: ListObject,
  resp: Types.Xhr.Response,
): Promise<void> {
  let savedRules: AbpObject = defaultValues.rules;
  if (features.fromValues)
    savedRules = (await values.rules()) ?? defaultValues.rules;
  debug && !speedOnly && logger("info", "XHR 响应", resp.responseText?.length);
  if (resp.responseText) {
    debug && speed("筛选规则", "start");
    savedRules[rule.标识] = rule.筛选后存储
      ? resp.responseText
          .split("\n")
          .filter((rule) => CRRE.test(rule) || isBasicRule(rule))
          .join("\n")
      : resp.responseText;
    debug && speed("筛选规则", "end");
    if (features.fromValues) await values.rules(savedRules);
    if (features.fromValues && savedRules[rule.标识].length > 0) {
      const etag = extrEtag(resp),
        savedEtags = (await values.etags()) ?? defaultValues.etags;
      if (etag) {
        savedEtags[rule.标识] = etag;
        await values.etags(savedEtags);
      }
    }
    data.receivedRules += "\n" + savedRules[rule.标识];
  }
  if (debug && !speedOnly)
    logger("data", {
      etags: await values.etags(),
      rules: countLength(savedRules),
    });
  return;
}

/**
 * 获取订阅规则：下载内容
 * @async
 * @param rule 订阅规则对象
 * @returns {Promise.<boolean>} 下载是否成功 (内容不为空)
 */
async function fetchRuleBody(rule: ListObject): Promise<boolean> {
  const url = addTimeParameter(rule.地址);
  const getResp = await promiseXhr({
    method: "GET",
    responseType: "text",
    url,
  }).catch((error: unknown) => {
    console.error("规则:", url, "下载错误:", error);
    debug &&
      !speedOnly &&
      data.alertLog &&
      logger("error", "规则: ", url, " 下载错误: ", error);
  });
  if (getResp?.responseText?.length) {
    await storeRule(rule, getResp);
    return true;
  } else {
    return false;
  }
}

/**
 * 获取订阅规则：判断 ETag 并更新
 * @async
 * @param rule 订阅规则对象
 * @param resp Response
 * @returns {Promise.<void>}
 */
async function fetchRuleGet(
  rule: ListObject,
  resp: Types.Xhr.Response,
): Promise<void> {
  const etag = extrEtag(resp),
    savedEtags = await values.etags();
  debug && !speedOnly && logger("info", "XHR HEAD 响应", resp);
  if (resp.responseText?.length) {
    debug && !speedOnly && logger("warn", "不支持 HEAD");
    await storeRule(rule, resp);
    return etag === savedEtags?.[rule.标识]
      ? Promise.reject(new Error("ETag 一致"))
      : undefined;
  } else {
    if (etag === savedEtags?.[rule.标识]) {
      throw new Error("ETag 一致");
    } else {
      debug &&
        !speedOnly &&
        logger("info", "ETag: ", etag, savedEtags?.[rule.标识]);
      return (await fetchRuleBody(rule))
        ? undefined
        : Promise.reject(new Error("GET 失败"));
    }
  }
}

/**
 * 获取订阅规则：获取 ETag
 * @async
 * @param rule 订阅规则对象
 * @returns {Promise.<void>}
 */
async function fetchRule(rule: ListObject): Promise<void> {
  debug && !speedOnly && logger("info", `在线规则 ${rule.标识} 开始`);
  let headRespError: XhrError = { error: "noxhr" };
  const url = addTimeParameter(rule.地址);
  const headResp = await promiseXhr({
    method: "HEAD",
    responseType: "text",
    url,
  }).catch((error: unknown) => {
    headRespError = error as XhrError;
    console.error("规则:", url, "HEAD 错误:", error);
    alertLog && logger("error", "规则: ", url, " HEAD 错误: ", error);
  });
  if (headResp) {
    await fetchRuleGet(rule, headResp);
    return;
  } else {
    // Via HEAD 会超时，但可以得到 ETag
    if (headRespError.resp?.responseHeaders) {
      await fetchRuleGet(rule, headRespError.resp);
    } else {
      throw new Error("HEAD 失败");
    }
    return;
  }
}

/**
 * 获取订阅规则：主函数
 * @async
 * @returns {Promise.<void>}
 */
async function fetchRules(): Promise<void> {
  let hasUpdate = preset.onlineRules.length;
  data.updating = true;
  if (features.menu) await gmMenu("update", () => void 0);
  debug &&
    !speedOnly &&
    logger("info", `在线规则 ${String(preset.onlineRules.length)} 个地址`);
  for (const rule of preset.onlineRules) {
    if (rule.在线更新) {
      debug && speed(`下载 ${rule.标识}`, "start");
      await fetchRule(rule).catch((error: unknown) => {
        console.error("获取规则", rule, "发生错误:", error);
        hasUpdate--;
      });
      debug && speed(`下载 ${rule.标识}`, "end");
    } else {
      hasUpdate--;
    }
  }
  debug && !speedOnly && logger("info", "在线规则下载结束");
  await values.time(new Date().toLocaleString("zh-CN"));
  data.updating = false;
  if (features.menu) await makeInitMenu();
  if (hasUpdate > 0) {
    for (const host of await getSavedHosts()) {
      if (host === location.hostname) {
        await initRules(true);
      } else {
        const save = await values.css(void 0, host);
        if (save) {
          save.needUpdate = true;
          await values.css(save, host);
        }
      }
    }
  }
  return;
}

/**
 * 更新订阅规则
 * @async
 * @param {boolean} force 无条件更新
 * @returns {Promise.<void>}
 */
export async function performUpdate(force: boolean): Promise<void> {
  const oldTime = new Date(
    (await values.time()) ?? defaultValues.time,
  ).getDate();
  const newTime = new Date().getDate();
  debug &&
    !speedOnly &&
    (force
      ? logger("color", "无条件更新", "darkslateblue")
      : logger("color", "日期变更更新", "darkslateblue", oldTime, newTime));
  return data.isFrame
    ? Promise.reject(new Error("是 iframe"))
    : force || oldTime !== newTime
      ? fetchRules()
      : Promise.resolve();
}
